package gateway.auth;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.sql.DataSource;
import java.security.Principal;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;

//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@ComponentScan
@RestController
@EnableAutoConfiguration
public class Application {

    @RequestMapping({"/user", "/me"})
    public Principal user(Principal principal) {
        OAuth2Authentication authentication = (OAuth2Authentication) principal;
        OAuth2AuthenticationDetails authenticationDetails = (OAuth2AuthenticationDetails) authentication.getDetails();
        Map<String, String> decodedDetails = (Map) authenticationDetails.getDecodedDetails();
        if (decodedDetails == null) {
            decodedDetails = new LinkedHashMap();
            authenticationDetails.setDecodedDetails(decodedDetails);
        }
        decodedDetails.put("gender", "female");
        return principal;
    }

    @Bean
    public UserDetailsService userDetailsService() {
        /*
        fill permission to SimpleGrantedAuthority  e.g.  new SimpleGrantedAuthority(permission.code);
        reference https://github.com/babylikebird/Micro-Service-Skeleton/blob/master/mss-oauth/src/main/java/com/microservice/skeleton/auth/service/impl/UserDetailsServiceImpl.java
         */
        return username -> new User(username, "acmesecret",
                true, true, true, true,
                new LinkedHashSet<GrantedAuthority>());
    }

    @Configuration
    protected static class SecurityConfigurer extends WebSecurityConfigurerAdapter {

        @SuppressWarnings("deprecation")
        @Bean
        public PasswordEncoder passwordEncoder() {
//            return new BCryptPasswordEncoder();
            return NoOpPasswordEncoder.getInstance();//for test
        }

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/**")
                    .authorizeRequests()
//                    .antMatchers("/", "/login**").permitAll()
                    .anyRequest().authenticated();
//                    .and().exceptionHandling()
//                    .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"));


//            http
//                    .requestMatchers().antMatchers("/**")
//                .and()
//                    .authorizeRequests().antMatchers("/user/add").access("#oauth2.hasScope('write')")
//                .and()
//                    .authorizeRequests().anyRequest().authenticated();
        }
    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

        @Autowired
        private DataSource dataSource;

        @Autowired
        private RedisConnectionFactory redisConnectionFactory;

        @Autowired
        private UserDetailsService userDetailsService;

        @Bean
        public ClientDetailsService clientDetails() {
            return new JdbcClientDetailsService(dataSource);
        }

        @Bean
        RedisTokenStore redisTokenStore() {
            return new RedisTokenStore(redisConnectionFactory);
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.withClientDetails(clientDetails());
        }

        @Primary
        @Bean
        public DefaultTokenServices defaultTokenServices() {
            DefaultTokenServices tokenServices = new DefaultTokenServices();
            tokenServices.setTokenStore(redisTokenStore());
            tokenServices.setSupportRefreshToken(true);
            tokenServices.setClientDetailsService(clientDetails());
            tokenServices.setAccessTokenValiditySeconds(60 * 60 * 2);//default 12 hours
            tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);//default 30 days
            return tokenServices;
        }

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            endpoints.tokenStore(redisTokenStore())
                    .userDetailsService(userDetailsService)
                    .tokenServices(defaultTokenServices());
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) {
            security.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')").checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')")
                    .allowFormAuthenticationForClients();

//            security.tokenKeyAccess("permitAll()").checkTokenAccess("permitAll()").allowFormAuthenticationForClients();//for test
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

